package com.bruce.tool.orm.mybatis.generator.plugin;

import com.bruce.tool.orm.mybatis.core.util.ColumnUtils;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.api.dom.java.*;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

/**
 * 功能 :
 * 1.example链式调用方法写入
 * 2.增加复杂查询条件的自动组装方法
 *
 * @author : Bruce(刘正航) 11:39 上午 2019/12/28
 */
public class ExampleExtendPlugin extends PluginAdapter {

    private Boolean likeListValue;

    @Override
    public boolean validate(List<String> warnings) {
        String likeListValue = super.getProperties().getProperty("likeListValue");
        this.likeListValue = Boolean.valueOf(likeListValue);
        return true;
    }

    @Override
    public boolean modelExampleClassGenerated(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        topLevelClass.getJavaDocLines().clear();
        topLevelClass.addJavaDocLine("@SuppressWarnings(\"all\")");
        List<Field> fields = topLevelClass.getFields();
        for (Field field : fields) {
            field.getJavaDocLines().clear();
        }
        List<InnerClass> classes = topLevelClass.getInnerClasses();
        for (InnerClass innerClass : classes) {
            innerClass.getJavaDocLines().clear();
            FullyQualifiedJavaType criteria = new FullyQualifiedJavaType("Criteria");
            FullyQualifiedJavaType criterion = new FullyQualifiedJavaType("Criterion");
            FullyQualifiedJavaType generatedCriteria = new FullyQualifiedJavaType("GeneratedCriteria");
            if (criteria.equals(innerClass.getType())) {
                // 添加链式编程代码
                this.addExampleMethod(topLevelClass, innerClass);
            }
            if (generatedCriteria.equals(innerClass.getType())) {
                if (this.likeListValue) {
                    this.addLikeListMethod(innerClass);
                    this.addlikeBaseMethod(innerClass);
                }
                this.removeException(innerClass);
            }
            if (criterion.equals(innerClass.getType()) && this.likeListValue) {
                this.addLikeListToCriterion(innerClass);
            }
        }
        List<Method> methods = topLevelClass.getMethods();
        int index = 0;
        for (Method method : methods) {
            method.getJavaDocLines().clear();
            if ("createCriteriaInternal".equals(method.getName())) {
                // 修改创建方法
                List<String> bodyLines = method.getBodyLines();
                if (CollectionUtils.isEmpty(bodyLines)) {
                    continue;
                }
                String newLine = "Criteria criteria = new Criteria(this);";
                bodyLines.remove(0);
                bodyLines.add(0, newLine);
            }
            if ("getOredCriteria".equals(method.getName())) {
                index = methods.indexOf(method);
            }
        }
        topLevelClass.getMethods().add(index, this.addConditionMethod(topLevelClass));
        topLevelClass.getMethods().add(index, this.addTransferMethod());
        if (this.likeListValue) {
            topLevelClass.getMethods().add(index, this.addTransferMethod2());
        }
        return true;
    }

    private void removeException(InnerClass innerClass) {
        List<Method> methods = innerClass.getMethods();
        for (Method method : methods) {
            if (!"addCriterion".equals(method.getName())) {
                continue;
            }
            List<String> bodyLines = method.getBodyLines();
            if (method.getParameters().size() == 3) {
                bodyLines.add(3, "}");
                bodyLines.add(3, "return;");
                bodyLines.add(3, "if((value instanceof List) && ((List)value).size() == 0){");

                bodyLines.add(3, "}");
                bodyLines.add(3, "return;");
                bodyLines.add(3, "if((value instanceof String) && ((String)value).trim().length() == 0){");
            }
            if (method.getParameters().size() == 4) {
                bodyLines.add(3, "}");
                bodyLines.add(3, "return;");
                bodyLines.add(3, "if((value2 instanceof String) && ((String)value2).trim().length() == 0){");

                bodyLines.add(3, "}");
                bodyLines.add(3, "return;");
                bodyLines.add(3, "if((value1 instanceof String) && ((String)value1).trim().length() == 0){");
            }
            int index = 0;
            for (String line : bodyLines) {
                if (!line.contains("RuntimeException")) {
                    continue;
                }
                index = bodyLines.indexOf(line);
            }
            bodyLines.remove(index);
            bodyLines.add(index, "return;");
        }
    }

    /**
     * 添加值转换的方法
     **/
    private Method addTransferMethod() {
        Method transfer = new Method("transfer");
        transfer.addParameter(new Parameter(new FullyQualifiedJavaType("Object"), "value"));
        transfer.setReturnType(new FullyQualifiedJavaType("Object"));
        transfer.setVisibility(JavaVisibility.PRIVATE);
        transfer.addBodyLine("if(value instanceof String){");
        transfer.addBodyLine("return \"\\\"\"+value+\"\\\"\";");
        transfer.addBodyLine("}");
        transfer.addBodyLine("if(value instanceof Date){");
        transfer.addBodyLine("return \"\\\"\"+ DateUtils.format((Date) value)+\"\\\"\";");
        transfer.addBodyLine("}");
        transfer.addBodyLine("if(value instanceof List){");
        transfer.addBodyLine("List line = (List) value;");
        transfer.addBodyLine("Object first = line.get(0);");
        transfer.addBodyLine("if(first instanceof String){");
        transfer.addBodyLine("return \"\\\"\" + StringUtils.join((List)value,\"\\\",\\\"\")+ \"\\\"\";");
        transfer.addBodyLine("}else if(value instanceof Date){");
        transfer.addBodyLine("List<String> valuestr = new ArrayList<>(line.size());");
        transfer.addBodyLine("for (Object date : line){");
        transfer.addBodyLine("valuestr.add(DateUtils.format((Date) date));");
        transfer.addBodyLine("}");
        transfer.addBodyLine("return \"\\\"\" + StringUtils.join((List) valuestr, \"\\\",\\\"\")+ \"\\\"\";");
        transfer.addBodyLine("}else{");
        transfer.addBodyLine("return StringUtils.join((List)value,\",\");");
        transfer.addBodyLine("}");
        transfer.addBodyLine("}");
        transfer.addBodyLine("return value;");
        return transfer;
    }

    private Method addTransferMethod2() {
        Method transfer = new Method("transfer");
        transfer.addParameter(new Parameter(new FullyQualifiedJavaType("String"), "condition"));
        transfer.addParameter(new Parameter(new FullyQualifiedJavaType("Object"), "values"));
        transfer.setReturnType(new FullyQualifiedJavaType("Object"));
        transfer.setVisibility(JavaVisibility.PRIVATE);
        transfer.addBodyLine("List line = (List) values;");
        transfer.addBodyLine("Object first = line.get(0);");
        transfer.addBodyLine("if(first instanceof String){");
        transfer.addBodyLine("return condition + \" \\\"\" + StringUtils.join((List)values,\"\\\" or \" + condition + \" \\\"\") + \"\\\"\";");
        transfer.addBodyLine("}");
        transfer.addBodyLine("if(first instanceof Date){");
        transfer.addBodyLine("List<String> valuestr = new ArrayList<>(line.size());");
        transfer.addBodyLine("for (Object date : line){");
        transfer.addBodyLine("valuestr.add(DateUtils.format((Date) date));");
        transfer.addBodyLine("}");
        transfer.addBodyLine("return condition + \" \\\"\" + StringUtils.join((List)valuestr,\"\\\" or \" + condition + \" \\\"\") + \"\\\"\";");
        transfer.addBodyLine("}");
        transfer.addBodyLine("return condition + \" \\\"\" + StringUtils.join((List)values,\" or \" + condition + \" \") + \"\";");
        return transfer;
    }

    /**
     * 添加条件拼接的方法
     **/
    private Method addConditionMethod(TopLevelClass topLevelClass) {
        String exampleName = topLevelClass.getType().getShortName();
        String domainName = exampleName.replace("Example", "");
        topLevelClass.addImportedType("java.util.Date");
        topLevelClass.addImportedType("com.bruce.tool.common.util.DateUtils");
        topLevelClass.addImportedType("org.apache.commons.lang3.StringUtils");
        topLevelClass.addImportedType("com.bruce.tool.orm.mybatis.core.query.Example");
        topLevelClass.addImportedType("com.bruce.tool.orm.mybatis.core.util.TableUtils");
        topLevelClass.addSuperInterface(new FullyQualifiedJavaType("Example<" + domainName + ">"));
        Method condition = new Method("getCondition");
        condition.addJavaDocLine("@Override");
        condition.setReturnType(new FullyQualifiedJavaType("String"));
        condition.setVisibility(JavaVisibility.PUBLIC);
        condition.addBodyLine("String tableName = TableUtils.getTableName(" + domainName + ".class);");
        condition.addBodyLine("String columnMapping = TableUtils.getColumnMapping(" + domainName + ".class);");
        condition.addBodyLine("StringBuilder sql = new StringBuilder(\"select \").append(columnMapping).append(\" from \").append(tableName);");
        condition.addBodyLine("StringBuilder conditions = new StringBuilder();");
        condition.addBodyLine("for (" + exampleName + ".Criteria criteria : this.oredCriteria){");
        condition.addBodyLine("List<" + exampleName + ".Criterion> criterions = criteria.getAllCriteria();");
        condition.addBodyLine("if(null==criterions||criterions.isEmpty()){ continue; }");
        condition.addBodyLine("int index = this.oredCriteria.indexOf(criteria);");
        condition.addBodyLine("StringBuilder condition = new StringBuilder();");
        condition.addBodyLine("for (" + exampleName + ".Criterion criterion : criterions){");
        condition.addBodyLine("if(criterion.isSingleValue() && !criterion.isNoValue() && null != criterion.getValue()){");
        condition.addBodyLine("if(criterions.indexOf(criterion) > 0){");
        condition.addBodyLine("condition.append(\" and \");");
        condition.addBodyLine("}");
        condition.addBodyLine("condition.append(criterion.getCondition())");
        condition.addBodyLine(".append(\" \")");
        condition.addBodyLine(".append(transfer(criterion.getValue()));");
        condition.addBodyLine("}");
        condition.addBodyLine("if(criterion.isBetweenValue() && null != criterion.getValue() && null != criterion.getSecondValue()){");
        condition.addBodyLine("if(criterions.indexOf(criterion) > 0){");
        condition.addBodyLine("condition.append(\" and \");");
        condition.addBodyLine("}");
        condition.addBodyLine("condition.append(criterion.getCondition())");
        condition.addBodyLine(".append(\" \")");
        condition.addBodyLine(".append(transfer(criterion.getValue()))");
        condition.addBodyLine(".append(\" and \")");
        condition.addBodyLine(".append(transfer(criterion.getSecondValue()));");
        condition.addBodyLine("}");
        condition.addBodyLine("if(criterion.isListValue() && null != criterion.getValue() && !((List)criterion.getValue()).isEmpty()){");
        condition.addBodyLine("if(criterions.indexOf(criterion) > 0){");
        condition.addBodyLine("condition.append(\" and \");");
        condition.addBodyLine("}");
        condition.addBodyLine("condition.append(criterion.getCondition())");
        condition.addBodyLine(".append(\"(\")");
        condition.addBodyLine(".append(transfer(criterion.getValue()))");
        condition.addBodyLine(".append(\")\");");
        condition.addBodyLine("}");
        if (this.likeListValue) {
            condition.addBodyLine("if(criterion.isLikeListValue() && null != criterion.getValue() && !((List)criterion.getValue()).isEmpty()){");
            condition.addBodyLine("if(criterions.indexOf(criterion) > 0){");
            condition.addBodyLine("condition.append(\" and \");");
            condition.addBodyLine("}");
            condition.addBodyLine("List<String> values = (List<String>) criterion.getValue();");
            condition.addBodyLine("condition.append(\"(\")");
            condition.addBodyLine(".append(transfer(criterion.getCondition(),values))");
            condition.addBodyLine(".append(\")\");");
            condition.addBodyLine("}");
        }
        condition.addBodyLine("}");
        condition.addBodyLine("if(null==condition || condition.toString().trim().length() == 0){");
        condition.addBodyLine("continue;");
        condition.addBodyLine("}");
        condition.addBodyLine("if(index > 0){");
        condition.addBodyLine("condition.insert(0,\" or (\");");
        condition.addBodyLine("}else{");
        condition.addBodyLine("condition.insert(0,\"(\");");
        condition.addBodyLine("}");
        condition.addBodyLine("condition.append(\")\");");
        condition.addBodyLine("conditions.append(condition);");
        condition.addBodyLine("}");
        condition.addBodyLine("if(null != conditions && conditions.length() > 0){");
        condition.addBodyLine("sql.append(\" where \").append(conditions);");
        condition.addBodyLine("} else {");
        condition.addBodyLine("sql.append(\" where 1=1 \");");
        condition.addBodyLine("}");
        condition.addBodyLine("if(null != this.orderByClause && this.orderByClause.length() > 0){");
        condition.addBodyLine("sql.append(\" order by \").append(this.orderByClause);");
        condition.addBodyLine("}");
        condition.addBodyLine("return sql.toString();");
        return condition;
    }

    /**
     * 添加get方法
     **/
    private void addExampleMethod(TopLevelClass topLevelClass, InnerClass innerClass) {
        Field field1 = new Field("example", topLevelClass.getType());
        field1.setVisibility(JavaVisibility.PRIVATE);
        innerClass.addField(field1);

        List<Method> methods = innerClass.getMethods();
        for (Method method : methods) {
            method.getJavaDocLines().clear();
            if (method.isConstructor()) {
                method.addParameter(new Parameter(topLevelClass.getType(), "example"));
                method.addBodyLine("this.example = example;");
            }
        }
        Method method = new Method("example");
        method.setVisibility(JavaVisibility.PUBLIC);
        method.addBodyLine("return this.example;");
        method.setReturnType(topLevelClass.getType());
        innerClass.addMethod(method);
    }

    /**
     * 增加like条件判断
     **/
    private void addLikeListMethod(InnerClass innerClass) {
        List<Method> methods = innerClass.getMethods();
        List<Method> newMethods = new ArrayList<>();
        for (Method method : methods) {
            if (!method.getName().startsWith("and")) {
                continue;
            }
            if (null == method.getParameters() || method.getParameters().size() == 0) {
                continue;
            }
            // 添加字符串非空判断
            if (method.getParameters().size() == 1) {
                Parameter parameter = method.getParameters().get(0);
                if (parameter.getType().equals(new FullyQualifiedJavaType("String"))) {
                    method.addBodyLine(0, "if(null==value || value.trim().length() == 0){ return (Criteria) this; }");
                }
            }
            if (method.getName().contains("NotLike")) {
                continue;
            }
            if (!method.getName().contains("Like")) {
                continue;
            }
            // 默认的like方法,添加前后缀
            Method prefixValue = createLikeMethod(method, "Prefix", "prefix");
            Method suffixValue = createLikeMethod(method, "Suffix", "suffix");

            method.addBodyLine(0, "if(value.indexOf(\"%\") < 0 ){ value = \"%\"+value+\"%\"; }");
            method.addBodyLine(0, "if(null == value || value.trim().length() == 0 ){ return (Criteria) this; }");

            Method prefixLikeValue = createLikeListMethod(method, "Prefix", "prefix");
            Method aourndLikeValue = createLikeListMethod(method, "", "around");
            Method suffixLikeValue = createLikeListMethod(method, "Suffix", "suffix");

            newMethods.add(prefixValue);
            newMethods.add(suffixValue);
            newMethods.add(prefixLikeValue);
            newMethods.add(aourndLikeValue);
            newMethods.add(suffixLikeValue);
        }
        for (Method method : newMethods) {
            innerClass.addMethod(method);
        }
        innerClass.getMethods().sort(Comparator.comparing(Method::getName));
    }

    private void addlikeBaseMethod(InnerClass innerClass) {
        Method transferLikeValue = new Method("transferLikeValue");
        transferLikeValue.setVisibility(JavaVisibility.PRIVATE);
        transferLikeValue.setReturnType(new FullyQualifiedJavaType("List<String>"));
        transferLikeValue.addParameter(new Parameter(new FullyQualifiedJavaType("List<String>"), " values"));
        transferLikeValue.addParameter(new Parameter(new FullyQualifiedJavaType("String"), "sign"));
        transferLikeValue.addBodyLine("List<String> likeValues = new ArrayList<>();");
        transferLikeValue.addBodyLine("for (String value : values){");
        transferLikeValue.addBodyLine("if(null==value||value.length()==0){continue;}");
        transferLikeValue.addBodyLine("if(value.indexOf(\"%\") >=0 ){ continue; }");
        transferLikeValue.addBodyLine("if(\"prefix\".equals(sign)){");
        transferLikeValue.addBodyLine("value = \"%\"+value;");
        transferLikeValue.addBodyLine("}");
        transferLikeValue.addBodyLine("if(\"suffix\".equals(sign)){");
        transferLikeValue.addBodyLine("value = value+\"%\";");
        transferLikeValue.addBodyLine("}");
        transferLikeValue.addBodyLine("if(\"around\".equals(sign)){");
        transferLikeValue.addBodyLine("value = \"%\"+value+\"%\";");
        transferLikeValue.addBodyLine("}");
        transferLikeValue.addBodyLine("likeValues.add(value);");
        transferLikeValue.addBodyLine("}");
        transferLikeValue.addBodyLine("return likeValues;");
        innerClass.addMethod(transferLikeValue);
    }

    private void addLikeListToCriterion(InnerClass innerClass) {
        // 添加属性
        Field listLikeField = new Field("likeListValue", new FullyQualifiedJavaType("boolean"));
        listLikeField.setVisibility(JavaVisibility.PRIVATE);
        innerClass.addField(listLikeField);

        // 添加get方法
        Method isListLikeValue = new Method("isLikeListValue");
        isListLikeValue.setVisibility(JavaVisibility.PUBLIC);
        isListLikeValue.setReturnType(new FullyQualifiedJavaType("boolean"));
        isListLikeValue.addBodyLine("return likeListValue;");
        innerClass.addMethod(isListLikeValue);

        // 改造构造方法
        List<Method> methods = innerClass.getMethods();
        for (Method method : methods) {
            if (!method.isConstructor()) {
                continue;
            }
            if (null == method.getParameters()) {
                continue;
            }
            if (method.getParameters().size() != 3) {
                continue;
            }
            FullyQualifiedJavaType first = method.getParameters().get(0).getType();
            FullyQualifiedJavaType second = method.getParameters().get(1).getType();
            FullyQualifiedJavaType third = method.getParameters().get(2).getType();
            FullyQualifiedJavaType string = new FullyQualifiedJavaType("java.lang.String");
            FullyQualifiedJavaType object = new FullyQualifiedJavaType("java.lang.Object");
            if (string.equals(first) && object.equals(second) && string.equals(third)) {
                method.getBodyLines().clear();
                method.addBodyLine("super();");
                method.addBodyLine("this.condition = condition;");
                method.addBodyLine("this.value = value;");
                method.addBodyLine("this.typeHandler = typeHandler;");
                method.addBodyLine("if (value instanceof List<?>) {");
                method.addBodyLine("if(condition.contains(\"like\")){");
                method.addBodyLine("this.likeListValue = true;");
                method.addBodyLine("}else{");
                method.addBodyLine("this.listValue = true;");
                method.addBodyLine("}");
                method.addBodyLine("} else {");
                method.addBodyLine("this.singleValue = true;");
                method.addBodyLine("}");
            }
        }
    }

    /**
     * 创建like方法
     **/
    private Method createLikeListMethod(Method method, String methodSuffix, String sign) {
        String methodName = method.getName();
        String fieldName = transferColumnName(methodName);
        Method listLikeValue = new Method(method.getName() + methodSuffix);
        listLikeValue.setVisibility(method.getVisibility());
        listLikeValue.setReturnType(method.getReturnType().orElse(null));
        listLikeValue.addParameter(new Parameter(new FullyQualifiedJavaType("List<String>"), "values"));
        listLikeValue.addBodyLine("if(null==values || values.isEmpty()){ return (Criteria) this; }");
        listLikeValue.addBodyLine("List<String> likeValues = transferLikeValue(values,\"" + sign + "\");");
        listLikeValue.addBodyLine("addCriterion(\"" + fieldName + " like\", likeValues, \"" + fieldName + "\");");
        listLikeValue.addBodyLine("return (Criteria) this;");
        return listLikeValue;
    }

    /**
     * 通过方法名,获取表字段列明(只适用于Like方法)
     **/
    private String transferColumnName(String methodName) {
        methodName = methodName.replaceAll("and", "");
        methodName = methodName.replaceAll("Like.*", "");
        StringBuilder field = new StringBuilder(methodName);
        field.setCharAt(0, Character.toUpperCase(field.charAt(0)));
        return ColumnUtils.humpToUnderline(field.toString());
    }

    /**
     * 创建like方法
     **/
    private Method createLikeMethod(Method method, String methodSuffix, String sign) {
        Method likeValue = new Method(method.getName() + methodSuffix);
        likeValue.setVisibility(method.getVisibility());
        likeValue.setReturnType(method.getReturnType().orElse(null));
        List<Parameter> parameters = method.getParameters();
        for (Parameter parameter : parameters) {
            likeValue.addParameter(parameter);
        }
        List<String> bodyLines = method.getBodyLines();
        for (String bodyLine : bodyLines) {
            likeValue.addBodyLine(bodyLine);
        }
        likeValue.addBodyLine(0, "if(null==value || value.trim().length() == 0){ return (Criteria) this; }");
        if ("suffix".equals(sign)) {
            likeValue.addBodyLine(1, "value = \"%\"+value;");
        }
        if ("prefix".equals(sign)) {
            likeValue.addBodyLine(1, "value = value+\"%\";");
        }
        return likeValue;
    }
}
